﻿//------------------------------------------------------------------------------
// Copyright © FRA & FV 2014
// All rights reserved
//------------------------------------------------------------------------------
// Binary Serialization Custom Tool
//
// SVN revision information:
//   $Revision: 100 $
//------------------------------------------------------------------------------
using System;
using System.CodeDom;
using System.Linq;
using FRAFV.Binary.Serialization.CodeDom;

namespace FRAFV.Binary.Serialization
{
	/// <summary>
	/// public long DataSize
	/// {
	/// 	get
	/// 	{
	/// 		long size = N1L + ... + N2L; //Property1 Property2
	/// 		if (Checker3)
	/// 			size += N3L; //Property3
	/// 		...
	/// 		return size;
	/// 	}
	/// }
	/// </summary>
	internal sealed class DataSizeGenerator : ClassGenerator.MethodGenerator
	{
		public DataSizeGenerator(ClassParser cls)
			: base(cls) { }

		protected override ClassGenerator.MethodContext CreateMember(ClassParser cls)
		{
			//long DataSize
			var code = new CodeMemberProperty
			{
				Name = "DataSize",
				HasGet = true,
				Type = new CodeTypeReference(typeof(long))
			};
			return new MethodContext(cls, code);
		}

		protected override void Init()
		{
			if (!context.Class.BinBlock.GetDataSizeMethod.IsNullEmpty())
			{
				//GetDataSizeMethod;
				Code.Snippet(context.Class.BinBlock.GetDataSizeMethod);
			}
			if (HasEncoding)
			{
				//var encoding = Encoding;
				Code.Variable("encoding", GetEncoding());
				if (HasCString)
				{
					//var encoder = new BinaryReader(new MemoryStream(), encoding);
					Code.Variable("encoder", context.Class.BinBlock.ResolveBinaryWriterType().AsType().CreateInvoke(
							typeof(System.IO.MemoryStream).CreateInvoke(), "encoding".AsVariable()));
				}
			}
			InitPrimitives();
		}

		private void InitPrimitives()
		{
			var pl = props.OfType<PropertyPrimitiveGenerator>();
			if (pl.Any())
			{
				CodeExpression sum;
				//long size = base.DataSize + Sum; //Properties
				Code.Comment(String.Join("+", pl.Select(prop => prop.Name)));
				sum = pl.Select(prop => prop.SingleFormat.AsSize()).SumValues();
				if (GenerateBaseCall)
				{
					sum = Base.Property("DataSize").Add(sum);
				}
				Code.Variable<long>("size", sum);
			}
			else
			{
				if (GenerateBaseCall)
					//long size = base.DataSize;
					Code.Variable<long>("size", Base.Property("DataSize"));
				else
					//long size = 0;
					Code.Variable<long>("size", 0L);
			}
		}

		protected override void Close()
		{
			//return size;
			Code.Return("size".AsVariable());
		}


		private sealed class MethodContext : ClassGenerator.MethodContext
		{
			public MethodContext(ClassParser cls, CodeMemberProperty member)
				: base(cls, member, member.GetStatements)
			{
				member.Attributes = MethodsAccess;
			}

			protected override string ResolveItemMethod()
			{
				return Class.BinBlock.GetItemSizeMethod;
			}
		}

		protected override ClassGenerator.PropertyGenerator CreateProperty(ClassParser.ClassProperty prop)
		{
			var context = new PropertyContext(this.context, prop);
			var single = context.SingleFormat;
			if (single != null && single.IsPrimitive)
			{
				if (!context.IsArray)
					return new PropertyPrimitiveGenerator(context);
				else
					return CreateSimpleArray(single);
			}
			else if (context.IsCustom)
			{
				return new PropertyCustomGenerator(context);
			}
			else if (context.IsItem)
			{
				return CreateItem(context);
			}
			else if (context.IsArrayItem)
			{
				return new PropertyArrayItemGenerator(context, CreateDataArray(context));
			}
			else
			{
				return new PropertyDataGenerator(context, CreateDataArray(context));
			}
		}

		private ClassGenerator.PropertyGenerator CreateSimpleArray(ClassGenerator.FormatContext context)
		{
			if (context.IsConditionArray)
			{
				return new PropertySimpleCollectionCustomGenerator(context);
			}
			else if (context.CountHasStorage)
			{
				if (context.Property.IsCollection)
					return new PropertySimpleCollectionGenerator(context);
				else
					return new PropertySimpleArrayGenerator(context);
			}
			else
			{
				if (context.Property.IsCollection)
					return new PropertySimpleCollectionCustomGenerator(context);
				else if (context.CountIsNumber)
					return new PropertySimpleArrayNumGenerator(context);
				else
					return new PropertySimpleArrayCustomGenerator(context);
			}
		}

		private ClassGenerator.PropertyGenerator CreateDataArray(PropertyContext context)
		{
			if (!context.IsArray) return null;
			if (context.IsConditionArray)
				return new PropertyCollectionCustomGenerator(context);
			else if (context.CountHasStorage)
			{
				if (context.Property.IsCollection)
					return new PropertyCollectionGenerator(context);
				else
					return new PropertyArrayGenerator(context);
			}
			else
			{
				if (context.Property.IsCollection)
					return new PropertyCollectionCustomGenerator(context);
				else if (context.CountIsNumber)
					return new PropertyArrayNumGenerator(context);
				else
					return new PropertyArrayCustomGenerator(context);
			}
		}

		private ClassGenerator.PropertyItemGenerator CreateItem(PropertyContext context)
		{
			if (this.HasItemMethod)
				switch (this.context.Class.BinBlock.ItemOrder)
				{
					case BinItemOrder.Sequence:
						return new PropertyAnyItemGenerator(context, CreateItemArray(context)); //single&repeat
					case BinItemOrder.Choice:
						if (!context.IsRepeatItem)
							return new PropertyChoiceItemGenerator(context, CreateItemArray(context));
						break;
					case BinItemOrder.Any:
						return new PropertyAnyItemGenerator(context, CreateItemArray(context)); //single&repeat
				}
			else
				switch (this.context.Class.BinBlock.ItemOrder)
				{
					case BinItemOrder.Sequence:
						return new PropertySequenceSampleGenerator(context, CreateItemArray(context)); //single&repeat
					case BinItemOrder.Choice:
						if (!context.IsRepeatItem)
							return new PropertyChoiceSampleGenerator(context, CreateItemArray(context));
						break;
				}
			throw new NotSupportedException();
		}

		private ClassGenerator.PropertyGenerator CreateItemArray(PropertyContext context)
		{
			return null;
		}

		private sealed class PropertyContext : ClassGenerator.PropertyContext
		{
			public PropertyContext(ClassGenerator.MethodContext context, ClassParser.ClassProperty prop)
				: base(context, prop) { }
		}

		private sealed class PropertyPrimitiveGenerator : ClassGenerator.PropertyGenerator
		{
			public PropertyPrimitiveGenerator(PropertyContext context)
				: base(context) { }

			protected override CodeStatementCollection GenerateProperty()
			{
				return null;
			}

			public BinFormat SingleFormat { get { return context.SingleFormat.Format; } }
		}

		private sealed class PropertyDataGenerator : ClassGenerator.PropertyDataGenerator
		{
			public PropertyDataGenerator(PropertyContext context, ClassGenerator.PropertyGenerator array)
				: base(context, array) { }

			protected override ClassGenerator.FormatGenerator CreateFormat(BinDataAttribute binData)
			{
				var context = new FormatContext(this.context, binData);
				switch (context.Format)
				{
					case BinFormat.CString:
						return new FormatCStringGenerator(context);
					case BinFormat.Binary:
						if (!context.LengthIsNumber)
							return new FormatBinaryGenerator(context);
						else
							return new FormatBinaryNumGenerator(context);
					case BinFormat.PString:
						if (!context.LengthIsNumber)
							return new FormatPStringGenerator(context);
						else
							return new FormatPStringNumGenerator(context);
					default:
						return new FormatPrimitiveGenerator(context);
				}
			}

		}

		//[BinCustom]
		private sealed class PropertyCustomGenerator : ClassGenerator.PropertyGenerator
		{
			public PropertyCustomGenerator(PropertyContext context)
				: base(context) { }

			protected override CodeStatementCollection GenerateProperty()
			{
				Statements
					.Comment(Name)
					//size += Size_Property();
					.Increment("size".AsVariable(),
					(BinCustom.GetDataSizeMethod ?? "Size_" + Name + "()").AsSnippet());
				return null;
			}

		}

		private abstract class PropertySimpleGenerator : ClassGenerator.PropertyGenerator
		{
			protected ClassGenerator.FormatContext singleFormat;

			public PropertySimpleGenerator(ClassGenerator.FormatContext context)
				: base(context)
			{
				this.singleFormat = context;
			}
		}

		//[BinArray(CountCustomMethod = "Number")] Type[] Property
		//[BinArray(CountCustomMethod = "Number")] ArrayType Property
		private sealed class PropertySimpleArrayNumGenerator : PropertySimpleGenerator
		{
			public PropertySimpleArrayNumGenerator(ClassGenerator.FormatContext context)
				: base(context) { }

			protected override CodeStatementCollection GenerateProperty()
			{
				Statements
					.Comment(Name)
					//size += N * M;
					.Increment("size".AsVariable(), CountNumber.AsExpression().Mul(singleFormat.Format.AsSize()));
				return null;
			}

		}

		//[BinArray(CountCustomMethod)] Type[] Property
		//[BinArray(CountCustomMethod)] ArrayType Property
		private class PropertySimpleArrayCustomGenerator : PropertySimpleGenerator
		{
			public PropertySimpleArrayCustomGenerator(ClassGenerator.FormatContext context)
				: base(context) { }

			protected override CodeStatementCollection GenerateProperty()
			{
				Statements
					.Comment(Name)
					//if (Property != null)
					.Condition(PropertyIsNotNull).TrueStatements
						//var buffer = (Type[])Property;
						.Variable("buffer", Name.AsField().Cast(ItemsType))
						//size += N * buffer.Length;
						.Increment("size".AsVariable(), "buffer".AsVariable().Property("Length")
							.Mul(singleFormat.Format.AsSize()));
				return null;
			}

		}

		//[BinArray(ConditionCustomMethod)] Collection<Type> Property
		//[BinArray(ConditionCustomMethod)] CollectionType Property
		//[BinArray(CountCustomMethod)] Collection<Type> Property
		private class PropertySimpleCollectionCustomGenerator : PropertySimpleGenerator
		{
			public PropertySimpleCollectionCustomGenerator(ClassGenerator.FormatContext context)
				: base(context) { }

			protected override CodeStatementCollection GenerateProperty()
			{
				Statements
					.Comment(Name)
					//if (true) {
					.Condition(true.AsExpression()).TrueStatements
						//var buffer = Property;
						.Variable("buffer", Name.AsField())
						//size += N * buffer.Count;
						.Increment("size".AsVariable(), Name.AsField().Property("Count")
							.Mul(singleFormat.Format.AsSize()));
				return null;
			}

		}

		//[BinArray(CountFormat)] Type[] Property
		//[BinArray(CountFormat, CountCustomMethod)] Type[] Property
		private sealed class PropertySimpleArrayGenerator : PropertySimpleArrayCustomGenerator
		{
			public PropertySimpleArrayGenerator(ClassGenerator.FormatContext context)
				: base(context) { }

			protected override CodeStatementCollection GenerateProperty()
			{
				base.GenerateProperty();

				//size += N; //Length
				Statements.Increment("size".AsVariable(), CountFormat.AsSize());
				return null;
			}

		}

		//[BinArray(CountFormat)] Collection<Type> Property
		//[BinArray(CountFormat)] CollectionType Property
		//[BinArray(CountFormat, CountCustomMethod)] Collection<Type> Property
		//[BinArray(CountFormat, CountCustomMethod)] CollectionType Property
		private sealed class PropertySimpleCollectionGenerator : PropertySimpleCollectionCustomGenerator
		{
			public PropertySimpleCollectionGenerator(ClassGenerator.FormatContext context)
				: base(context) { }

			protected override CodeStatementCollection GenerateProperty()
			{
				base.GenerateProperty();

				//size += N; //Length
				Statements.Increment("size".AsVariable(), CountFormat.AsSize());
				return null;
			}

		}

		//[BinArray(CountFormat)] Type[] Property
		//[BinArray(CountFormat, CountCustomMethod)] Type[] Property
		private sealed class PropertyArrayGenerator : PropertyArrayCustomGenerator
		{
			public PropertyArrayGenerator(PropertyContext context)
				: base(context) { }

			protected override CodeStatementCollection GenerateProperty()
			{
				Statements
					//size += N; //Length
					.Increment("size".AsVariable(), CountFormat.AsSize());

				return base.GenerateProperty();
			}

		}

		//[BinArray(CountFormat)] Collection<Type> Property
		//[BinArray(CountFormat)] CollectionType Property
		//[BinArray(CountFormat, CountCustomMethod)] Collection<Type> Property
		//[BinArray(CountFormat, CountCustomMethod)] CollectionType Property
		private sealed class PropertyCollectionGenerator : ClassGenerator.PropertyGenerator
		{
			public PropertyCollectionGenerator(PropertyContext context)
				: base(context) { }

			protected override CodeStatementCollection GenerateProperty()
			{
				return Statements
					//size += N; //Length
					.Increment("size".AsVariable(), CountFormat.AsSize())
					//foreach(var child in Property)
					.ForEach(context.Property, "item").Statements;
			}

		}

		//[BinArray(CountCustomMethod)] Type[] Property
		//[BinArray(CountCustomMethod)] ArrayType Property
		private class PropertyArrayCustomGenerator : ClassGenerator.PropertyGenerator
		{
			public PropertyArrayCustomGenerator(PropertyContext context)
				: base(context) { }

			protected override CodeStatementCollection GenerateProperty()
			{
				return Statements
					//if (Property != null)
					.Condition(PropertyIsNotNull).TrueStatements
						//var buffer = (Type[])Property;
						.Variable("buffer", Name.AsField().Cast(ItemsType))
						//for(int index = 0; index < buffer.Length; index++)
						.ForCount("index", "buffer".AsVariable().Property("Length")).Statements;
			}

		}

		//[BinArray(CountCustomMethod = "Number")] Type[] Property
		//[BinArray(CountCustomMethod = "Number")] ArrayType Property
		private sealed class PropertyArrayNumGenerator : ClassGenerator.PropertyGenerator
		{
			public PropertyArrayNumGenerator(PropertyContext context)
				: base(context) { }

			protected override CodeStatementCollection GenerateProperty()
			{
				//(Type[])Property
				CodeExpression bufValue = Name.AsField().Cast(ItemsType);
				if (!context.IsArrayItem)
					//(Type[])Property ?? new Type[N]
					bufValue = bufValue.Coalesce(Type.CreateArray(CountNumber));

				return Statements
					//if (true) {
					.Condition(true.AsExpression()).TrueStatements
						//var buffer = (Type[])Property ?? new Type[N];
						.Variable("buffer", bufValue)
						//for(int index = 0; index < N; index++)
						.ForCount("index", CountNumber.AsExpression()).Statements;
			}

		}

		//[BinArray(ConditionCustomMethod)] Collection<Type> Property
		//[BinArray(ConditionCustomMethod)] CollectionType Property
		//[BinArray(CountCustomMethod)] Collection<Type> Property
		private sealed class PropertyCollectionCustomGenerator : ClassGenerator.PropertyGenerator
		{
			public PropertyCollectionCustomGenerator(PropertyContext context)
				: base(context) { }

			protected override CodeStatementCollection GenerateProperty()
			{
				//only custom count as non number implemented
				return Statements
					//foreach(var item in Property)
					.ForEach(context.Property, "item").Statements;
			}

		}

		//[BinArray, BinArrayItem]
		private sealed class PropertyArrayItemGenerator : ClassGenerator.PropertyArrayItemGenerator
		{
			public PropertyArrayItemGenerator(PropertyContext context, ClassGenerator.PropertyGenerator array)
				: base(context, array) { }

			protected override CodeStatementCollection GenerateData()
			{
				var item = ClassGenerator.FormatGenerator.ResolveArrayItem(context.Property);

				Statements
					.Comment(Name)
					//size += item.DataSize;
					.Increment("size".AsVariable(), item.Property("DataSize"));
				return null;
			}

		}

		//[BinBlock(ItemOrder = BinItemOrder.Choice, xItemMethod)]
		private class PropertyChoiceItemGenerator : ClassGenerator.PropertyItemGenerator
		{
			public PropertyChoiceItemGenerator(PropertyContext context, ClassGenerator.PropertyGenerator array)
				: base(context, array) { }

			protected override CodeStatementCollection GenerateData()
			{
				//if (Property != null) {
				var cond = Statements.Condition(PropertyIsNotNull);
				cond.TrueStatements
					.Comment(Name)
					//var item = Property;
					.Variable("item", Name.AsField())
					//size += Size_Item;
					.Increment("size".AsVariable(), ItemMethod);

				//} else {
				return cond.FalseStatements;
			}

		}

		//[BinBlock(ItemOrder = BinItemOrder.Any, xItemMethod)]
		private sealed class PropertyAnyItemGenerator : PropertyChoiceItemGenerator
		{
			public PropertyAnyItemGenerator(PropertyContext context, ClassGenerator.PropertyGenerator array)
				: base(context, array) { }

			protected override CodeStatementCollection GenerateData()
			{
				if (context.IsRepeatItem)
				{
					//foreach(var item in Property) {
					Statements.ForEach(context.Property, "item").Statements
						.Comment(Name)
						//size += Size_Item;
						.Increment("size".AsVariable(), ItemMethod);
				}
				else
				{
					var next = base.GenerateData();
					if (!context.IsRequiredItem) return null;

					//} else {
					next
						.Comment(Name)
						//var item = new Type();
						.Variable("item", RequiredItemType.CreateInvoke())
						//item.InitBinary();
						.MethodInvoke("item".AsVariable(), "InitBinary")
						//size += Size_Item;
						.Increment("size".AsVariable(), ItemMethod);
				}
				return null;
			}

		}

		//[BinBlock(ItemOrder = BinItemOrder.Choice)]
		private class PropertyChoiceSampleGenerator : ClassGenerator.PropertyItemGenerator
		{
			public PropertyChoiceSampleGenerator(PropertyContext context, ClassGenerator.PropertyGenerator array)
				: base(context, array) { }

			protected override CodeStatementCollection GenerateData()
			{
				//if (Property != null) {
				var cond = Statements.Condition(PropertyIsNotNull);
				cond.TrueStatements
					.Comment(Name)
					//size += Property.DataSize;
					.Increment("size".AsVariable(), Name.AsField().Property("DataSize"));

				//} else {
				return cond.FalseStatements;
			}

		}

		//[BinBlock(ItemOrder = BinItemOrder.Sequence)]
		private sealed class PropertySequenceSampleGenerator : PropertyChoiceSampleGenerator
		{
			public PropertySequenceSampleGenerator(PropertyContext context, ClassGenerator.PropertyGenerator array)
				: base(context, array) { }

			protected override CodeStatementCollection GenerateData()
			{
				if (context.IsRepeatItem)
				{
					//foreach(var item in Property) {
					Statements.ForEach(context.Property, "item").Statements
						.Comment(Name)
						//size += item.DataSize;
						.Increment("size".AsVariable(), "item".AsVariable().Property("DataSize"));
				}
				else
				{
					var next = base.GenerateData();
					if (!context.IsRequiredItem) return null;

					//} else {
					next
						.Comment(Name)
						//var item = new Type();
						.Variable("item", RequiredItemType.CreateInvoke())
						//item.InitBinary();
						.MethodInvoke("item".AsVariable(), "InitBinary")
						//size += item.DataSize;
						.Increment("size".AsVariable(), "item".AsVariable().Property("DataSize"));
				}
				return null;
			}
		}

		private sealed class FormatContext : ClassGenerator.FormatContext
		{
			public FormatContext(ClassGenerator.PropertyContext context, BinDataAttribute binData)
				: base(context, binData) { }
		}

		//[BinData]
		private sealed class FormatPrimitiveGenerator : ClassGenerator.FormatGenerator
		{
			public FormatPrimitiveGenerator(FormatContext context)
				: base(context) { }

			protected override void GenerateData()
			{
				//size += N; //Property
				Statements
					.Comment(PropertyName)
					.Increment("size".AsVariable(), Format.AsSize());
			}
		}

		//[BinData(BinFormat.CString)]
		private sealed class FormatCStringGenerator : ClassGenerator.FormatGenerator
		{
			public FormatCStringGenerator(FormatContext context)
				: base(context) { }

			protected override void GenerateData()
			{
				Type arg;
				Statements
					.Comment(PropertyName)
					//size -= encoder.BaseStream.Position;
					.Decrement("size".AsVariable(),
						"encoder".AsVariable().Property("BaseStream").Property("Position"))
					//encoder.WriteProperty((Type)Property);
					.MethodInvoke("encoder".AsVariable(), Format.ResolveWrite(out arg), GetItemCast)
					//size += encoder.BaseStream.Position;
					.Increment("size".AsVariable(),
						"encoder".AsVariable().Property("BaseStream").Property("Position"));
			}
		}

		//[BinData(BinFormat.Binary, LengthFormat)]
		//[BinData(BinFormat.Binary, LengthFormat, LengthCustomMethod)]
		private sealed class FormatBinaryGenerator : ClassGenerator.FormatGenerator
		{
			public FormatBinaryGenerator(FormatContext context)
				: base(context) { }

			protected override void GenerateData()
			{
				Statements
					.Comment(PropertyName)
					//if (Property != null)
					.Condition(PropertyIsNotNull).TrueStatements
					//size += ((byte[])Property).Length;
						.Increment("size".AsVariable(), GetItemCast.Property("Length"));
				if (context.LengthHasStorage)
					//size += N; //Length
					Statements.Increment("size".AsVariable(), LengthFormat.AsSize());
			}
		}

		//[BinData(BinFormat.PString, LengthFormat)]
		//[BinData(BinFormat.PString, LengthFormat, LengthCustomMethod)]
		private sealed class FormatPStringGenerator : ClassGenerator.FormatGenerator
		{
			public FormatPStringGenerator(FormatContext context)
				: base(context) { }

			protected override void GenerateData()
			{
				Statements
					.Comment(PropertyName)
					//if (Property != null)
					.Condition(PropertyIsNotNull).TrueStatements
						//size += encoding.GetByteCount((char[])Property);
						.Increment("size".AsVariable(),
							"encoding".AsVariable().MethodInvoke("GetByteCount", GetItemCast));
				if (context.LengthHasStorage)
					//size += N; //Length
					Statements.Increment("size".AsVariable(), LengthFormat.AsSize());
			}
		}

		//[BinData(BinFormat.Binary, LengthCustomMethod = "Number")]
		private sealed class FormatBinaryNumGenerator : ClassGenerator.FormatGenerator
		{
			public FormatBinaryNumGenerator(FormatContext context)
				: base(context) { }

			protected override void GenerateData()
			{
				Statements
					.Comment(PropertyName)
					//size += N;
					.Increment("size".AsVariable(), LengthNumber);
			}
		}

		//[BinData(BinFormat.PString, LengthCustomMethod = "Number")]
		private sealed class FormatPStringNumGenerator : ClassGenerator.FormatGenerator
		{
			public FormatPStringNumGenerator(FormatContext context)
				: base(context) { }

			protected override void GenerateData()
			{
				Statements
					.Comment(PropertyName)
					//size += encoding.GetByteCount((char[])Property ?? new char[N], 0, N);
					.Increment("size".AsVariable(),
						"encoding".AsVariable().MethodInvoke("GetByteCount", GetItemCast
							.Coalesce(Format.AsType().GetElementType().CreateArray(LengthNumber)), 0, LengthNumber));
			}
		}
	}
}
